require "import"
import "android.widget.*"
import "android.view.*"
import "android.graphics.Color"
import "android.graphics.Typeface"
import "android.graphics.Rect"
import "android.content.Context"
import "android.content.Intent"
import "android.net.Uri"
import "java.util.Locale"
import "android.speech.SpeechRecognizer"
import "android.speech.RecognitionListener"
import "android.speech.RecognizerIntent"
import "android.os.Bundle"
import "android.os.Handler"
import "android.os.Looper"
import "android.view.accessibility.AccessibilityNodeInfo"
import "android.media.ToneGenerator"
import "android.media.AudioManager"
import "android.media.RingtoneManager"
import "android.media.Ringtone"
import "android.provider.ContactsContract"
import "android.app.AlertDialog"
import "java.net.HttpURLConnection"
import "java.net.URL"
import "java.io.BufferedReader"
import "java.io.InputStreamReader"
import "java.net.URLEncoder"
import "java.util.Calendar"
import "java.util.GregorianCalendar"
import "android.icu.util.IslamicCalendar"
import "android.icu.util.ULocale"
import "android.speech.tts.TextToSpeech"
import "android.speech.tts.UtteranceProgressListener"
import "android.provider.Settings"

import "android.hardware.Sensor"
import "android.hardware.SensorEvent"
import "android.hardware.SensorEventListener"
import "android.hardware.SensorManager"
import "android.os.Vibrator"
import "android.location.Location"
import "android.location.LocationManager"
import "android.location.LocationListener"
import "java.lang.reflect.Array"
import "java.lang.Float"
import "java.util.HashMap"

-- 1️⃣ جدول التشكيل المحلي الفوري للأذكار والتحايا
local localTashkeelMap = {
  ["الاسلام عليكم ورحمة الله وبركاته"] = "الَّسَّلامُ عَلَيْكُمْ وَرَحْمَةُ اللهِ وَبَرَكَاتُهُ",
  ["السلام عليكم ورحمة الله وبركاته"] = "الَّسَّلامُ عَلَيْكُمْ وَرَحْمَةُ اللهِ وَبَرَكَاتُهُ",
  ["السلام عليكم ورحمة الله"] = "الَّسَّلامُ عَلَيْكُمْ وَرَحْمَةُ اللهِ",
  ["السلام عليكم"] = "الَّسَّلامُ عَلَيْكُمْ",
  ["بسم الله الرحمن الرحيم"] = "بِسْمِ اللهِ الرَّحْمٰنِ الرَّحِيمِ",
  ["لا اله الا الله"] = "لَا إِلٰهَ إِلَّا اللهُ",
  ["لا إله إلا الله"] = "لَا إِلٰهَ إِلَّا اللهُ",
  ["محمد رسول الله"] = "مُحَمَّدٌ رَسُولُ اللهِ",
  ["الحمد لله رب العالمين"] = "الْحَمْدُ للهِ رَبِّ الْعَالَمِينَ",
  ["لا حول ولا قوة الا بالله"] = "لَا حَوْلَ وَلَا قُوَّةَ إِلَّا بِاللهِ",
  ["لا حول ولا قوة إلا بالله"] = "لَا حَوْلَ وَلَا قُوَّةَ إِلَّا بِاللهِ",
  ["استغفر الله العظيم"] = "أَسْتَغْفِرُ اللهَ الْعَظِيمَ",
  ["سبحان الله وبحمده سبحان الله العظيم"] = "سُبْحَانَ اللهِ وَبِحَمْدِهِ سُبْحَانَ اللهِ الْعَظِيمِ",
  ["سبحان الله"] = "سُبْحَانَ اللهِ",
  ["الحمد لله"] = "الْحَمْدُ للهِ",
  ["الله اكبر"] = "اللهُ أَكْبَرُ",
  ["الله أكبر"] = "اللهُ أَكْبَرُ",
  ["صلى الله عليه وسلم"] = "صَلَّى اللهُ عَلَيْهِ وَسَلَّمَ",
  ["عليه الصلاة والسلام"] = "عَلَيْهِ الصَّلَاةُ وَالسَّلَامُ",
  ["اللهم صلي على محمد"] = "اللَّهُمَّ صَلِّ عَلَى مُحَمَّدٍ",
  ["اللهم صل على محمد"] = "اللَّهُمَّ صَلِّ عَلَى مُحَمَّدٍ",
  ["رضي الله عنه"] = "رَضِيَ اللهُ عَنْهُ",
  ["رحمه الله"] = "رَحِمَهُ اللهُ",
  ["رحمة الله"] = "رَحْمَةُ اللهِ",
  ["بركاته"] = "بَرَكَاتُهُ",
  ["وبركاته"] = "وَبَرَكَاتُهُ",
  ["الله"] = "اللهِ",
  ["العافية"] = "الْعَافِيَةُ",
  ["كرة"] = "كُرَةُ",
  ["الكرة"] = "الْكُرَةُ",
  ["مرحبا"] = "مَرْحَبًا",
  ["أهلا"] = "أَهْلًا"
}

local appTranslationMap = {
  ["يوتيوب"] = "com.google.android.youtube", ["يوتيب"] = "com.google.android.youtube", ["youtube"] = "com.google.android.youtube",
  ["تيك توك"] = "com.zhiliaoapp.musically", ["تيكتوك"] = "com.zhiliaoapp.musically", ["tiktok"] = "com.zhiliaoapp.musically",
  ["ماسنحر"] = "com.facebook.orca", ["مسنجر"] = "com.facebook.orca", ["messenger"] = "com.facebook.orca",
  ["فيسبوك"] = "com.facebook.katana", ["فايسبوك"] = "com.facebook.katana", ["facebook"] = "com.facebook.katana",
  ["واتساب"] = "com.whatsapp", ["واتس اب"] = "com.whatsapp", ["وتساب"] = "com.whatsapp", ["الواتساب"] = "com.whatsapp", ["الوتساب"] = "com.whatsapp", ["whatsapp"] = "com.whatsapp",
  ["انستغرام"] = "com.instagram.android", ["انستقرام"] = "com.instagram.android", ["instagram"] = "com.instagram.android",
  ["تلغرام"] = "org.telegram.messenger", ["تيليجرام"] = "org.telegram.messenger", ["telegram"] = "org.telegram.messenger",
  ["الهاتف"] = "com.android.server.telecom", ["الاتصال"] = "com.android.dialer",
  ["الضبط"] = "com.android.settings", ["الاعدادات"] = "com.android.settings", ["إعدادات"] = "com.android.settings", ["settings"] = "com.android.settings",
  ["الرسائل"] = "com.google.android.apps.messaging", ["رسائل"] = "com.google.android.apps.messaging",
  ["كروم"] = "com.android.chrome", ["جوحل كروم"] = "com.android.chrome", ["chrome"] = "com.android.chrome",
  ["جوحل"] = "com.google.android.googlequicksearchbox", ["google"] = "com.google.android.googlequicksearchbox",
  ["جيميل"] = "com.google.android.gm", ["جي ميل"] = "com.google.android.gm", ["gmail"] = "com.google.android.gm"
}

local emojiMap = {
  ["قلب"]="❤️", ["coeur"]="❤️", ["heart"]="❤️", ["حب"]="❤️", ["amour"]="❤️", ["love"]="❤️",
  ["نار"]="🔥", ["feu"]="🔥", ["fire"]="🔥", ["روعة"]="🔥", ["ستيكرز"]="✨", ["ملصق"]="✨",
  ["ضاحك"]="😂", ["يضحك"]="😀", ["ضحك"]="😀", ["ابتسامة"]="😃", ["مبتسم"]="😃", ["سعيد"]="😃",
  ["rire"]="😀", ["souris"]="😃", ["sourire"]="😃", ["happy"]="😃", ["laugh"]="😀", ["smile"]="😃",
  ["رجل"]="👨", ["امرأة"]="👩", ["ولد"]="👦", ["بنت"]="👧", ["أشخاص"]="👪", ["صديق"]="🤝",
  ["homme"]="👨", ["femme"]="👩", ["garçon"]="👦", ["fille"]="👧", ["ami"]="🤝",
  ["man"]="👨", ["woman"]="👩", ["boy"]="👦", ["girl"]="👧", ["friend"]="🤝",
  ["أسد"]="🦁", ["نمر"]="🐯", ["قطة"]="🐱", ["قط"]="🐱", ["كلب"]="🐶", ["ذئب"]="🐺", ["ثعلب"]="🦊",
  ["دب"]="🐻", ["باندا"]="🐼", ["قرد"]="🐵", ["حصان"]="🐴", ["خروف"]="🐑", ["طائر"]="🐦", ["عصفور"]="🐦",
  ["lion"]="🦁", ["tigre"]="🐯", ["chat"]="🐱", ["chien"]="🐶", ["loup"]="🐺", ["renard"]="🦊", ["ours"]="🐻",
  ["singe"]="🐵", ["cheval"]="🐴", ["oiseau"]="🐦", ["tiger"]="🐯", ["cat"]="🐱", ["dog"]="🐶", ["wolf"]="🐺",
  ["fox"]="🦊", ["bear"]="🐻", ["monkey"]="🐵", ["horse"]="🐴", ["bird"]="🐦",
  ["خبز"]="🍞", ["تفاح"]="🍎", ["تفاحة"]="🍎", ["موز"]="🍌", ["بيتزا"]="🍕", ["لحم"]="🥩", ["قهوة"]="☕",
  ["pain"]="🍞", ["pomme"]="🍎", ["banane"]="🍌", ["pizza"]="🍕", ["viante"]="🥩", ["café"]="☕",
  ["bread"]="🍞", ["apple"]="🍎", ["banana"]="🍌", ["pizza"]="🍕", ["meat"]="🥩", ["coffee"]="☕",
  ["مسجد"]="🕌", ["بيت"]="🏠", ["منزل"]="🏠", ["دار"]="🏠", ["سيارة"]="🚗", ["السيارة"]="🚗", ["طائرة"]="✈️", ["مستشفى"]="🏥", ["مدرسة"]="🏫",
  ["mosquée"]="🕌", ["maison"]="🏠", ["voiture"]="🚗", ["avion"]="✈️", ["hôpital"]="🏥", ["école"]="🏫",
  ["mosque"]="🕌", ["house"]="🏠", ["car"]="🚗", ["plane"]="✈️", ["hospital"]="🏫", ["school"]="🏫",
  ["سفر"]="✈️", ["رحلة"]="✈️", ["voyage"]="✈️", ["travel"]="✈️",
  ["سكين"]="🔪", ["السكين"]="🔪", ["قلم"]="✏️", ["كتاب"]="📖", ["هاتف"]="📱", ["تلفون"]="📱", ["شاشة"]="🖥️", ["كمبيوتر"]="💻",
  ["couteau"]="🔪", ["stylo"]="✏️", ["livre"]="📖", ["téléphone"]="📱", ["knife"]="🔪", ["pen"]="✏️", ["book"]="📖", ["phone"]="📱",
  ["رمز"]="⚠️", ["تنبيه"]="⚠️", ["علم"]="🇩🇿", ["الجزائر"]="🇩🇿", ["algeria"]="🇩🇿", ["algérie"]="🇩🇿", ["كرة"]="⚽", ["الكرة"]="⚽", ["ball"]="⚽", ["ballon"]="⚽"
}

function string:trim()
  return self:match("^%s*(.-)%s*$")
end

local function decodeUnicode(str)
  if not str then return "" end
  return str:gsub("\\u([0-9a-fA-F]{4})", function(h)
    local num = tonumber(h, 16)
    if num < 0x80 then
      return string.char(num)
    elseif num < 0x800 then
      return string.char(0xc0 + math.floor(num / 0x40), 0x80 + (num % 0x40))
    else
      return string.char(0xe0 + math.floor(num / 0x1000), 0x80 + (math.floor(num / 0x40) % 0x40), 0x80 + (num % 0x40))
    end
  end)
end

function checkAndEndCallDirectly()
  local root = service.getRootInActiveWindow()
  if not root then return false end
  local currentPackage = tostring(root.getPackageName() or "")
  local currentEdit = service.getEditText()
  if currentEdit and (currentPackage:find("orca") or currentPackage:find("whatsapp") or currentPackage:find("telegram")) then
    return false 
  end
  local keywords = {
    "إنهاء", "قطع المكالمة", "قطع", "رفض", "المكالمة", "الأحمر", "الاحمر",
    "raccrocher", "refuser", "fin", "décliner", "annuler l'appel",
    "end", "hangup", "decline", "disconnect", "call_end", "end_call", "red"
  }
  local callIds = {
    "com.facebook.orca:id/end_call_button", "com.whatsapp:id/send", 
    "com.whatsapp:id/hangup_button", "com.whatsapp:id/call_hangup_button",
    "org.telegram.messenger:id/call_end_button", "org.telegram.messenger:id/hud_cancel",
    "com.android.server.telecom:id/disconnect_button", "com.android.dialer:id/incall_end_call",
    "com.samsung.android.incallui:id/disconnect_button", "com.google.android.dialer:id/incall_end_call"
  }
  for _, id in ipairs(callIds) do
    local nodes = root.findAccessibilityNodeInfosByViewId(id)
    if nodes and not nodes.isEmpty() then
      for i = 0, nodes.size() - 1 do
        local node = nodes.get(i)
        if node.isClickable() then
          node.performAction(AccessibilityNodeInfo.ACTION_CLICK)
          service.speak("تم إنشاء المكالمة")
          return true
        elseif node.getParent() and node.getParent().isClickable() then
          node.getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK)
          service.speak("تم إنشاء المكالمة")
          return true
        end
      end
    end
  end
  for _, key in ipairs(keywords) do
    local nodes = root.findAccessibilityNodeInfosByText(key)
    if nodes and not nodes.isEmpty() then
      for i = 0, nodes.size() - 1 do
        local node = nodes.get(i)
        if node.isClickable() then
          node.performAction(AccessibilityNodeInfo.ACTION_CLICK)
          service.speak("تم إنشاء المكالمة")
          return true
        elseif node.getParent() and node.getParent().isClickable() then
          node.getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK)
          service.speak("تم إنشاء المكالمة")
          return true
        end
      end
    end
  end
  return false
end

if checkAndEndCallDirectly() then return true end

if _G.QiblaActive == true then
  _G.QiblaActive = false
  if _G.sensorManager and _G.qiblaListener then
    _G.sensorManager.unregisterListener(_G.qiblaListener)
    _G.qiblaListener = nil
  end
  _G.ContinuousDictationRunning = false
  if _G.GlobalRecognizer then pcall(function() _G.GlobalRecognizer.destroy() end) _G.GlobalRecognizer = nil end
  service.speak("تم إغلاق البوصلة وإيقاف الإضافة بالكامل")
  return true
end

if _G.ContinuousDictationRunning == true then
  local rootWindow = service.getRootInActiveWindow()
  local currentPackage = rootWindow and tostring(rootWindow.getPackageName()) or ""
  local isHomeScreen = false
  if currentPackage:find("launcher") or currentPackage:find("twogiga") or currentPackage:find("home") then
    isHomeScreen = true
  end
  if not isHomeScreen then
    _G.ContinuousDictationRunning = false
    if _G.GlobalRecognizer then pcall(function() _G.GlobalRecognizer.destroy() end) _G.GlobalRecognizer = nil end
    service.speak("تم إيقاف الإضافة نهائياً")
    return true
  end
end

_G.ContinuousDictationRunning = true
_G.DictationLang = "ar-SA" 
_G.TranslationMode = false        
_G.TargetTranslationLang = "en"   
_G.QiblaActive = false

local context = service
local recognizer = nil
local toneGen = nil
local internalTts = nil  

_G.sensorManager = service.getSystemService(Context.SENSOR_SERVICE)
local vibrator = service.getSystemService(Context.VIBRATOR_SERVICE)
local locationManager = service.getSystemService(Context.LOCATION_SERVICE)

local accelerometerSensor = _G.sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
local magneticSensor = _G.sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD)
_G.qiblaListener = nil

pcall(function() 
  toneGen = ToneGenerator(AudioManager.STREAM_MUSIC, 100) 
end)

function playSystemBeep()
  local success = false
  if toneGen then
    pcall(function()
      toneGen.startTone(ToneGenerator.TONE_PROP_BEEP)
      success = true
    end)
  end
  if not success then
    pcall(function()
      local notificationUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
      local r = RingtoneManager.getRingtone(service, notificationUri)
      if r then r.play() end
    end)
  end
end

function getUserCoordinates()
  local defaultLat, defaultLon = 34.8611, -1.7267 
  if not locationManager then return defaultLat, defaultLon end
  pcall(function()
    local providers = {"gps", "network", "passive"}
    for _, provider in ipairs(providers) do
      if locationManager.isProviderEnabled(provider) then
        local loc = locationManager.getLastKnownLocation(provider)
        if loc then
          defaultLat = loc.getLatitude()
          defaultLon = loc.getLongitude()
          break
        end
      end
    end
  end)
  return defaultLat, defaultLon end

function calculateQiblaDegree(lat, lon)
  local makkahLat = math.rad(21.4225)
  local makkahLon = math.rad(39.8262)
  local userLat = math.rad(lat)
  local userLon = math.rad(lon)
  local deltaLon = makkahLon - userLon
  local y = math.sin(deltaLon)
  local x = math.cos(userLat) * math.tan(makkahLat) - math.sin(userLat) * math.cos(deltaLon)
  local qiblaRad = math.atan2(y, x)
  local qiblaDeg = math.deg(qiblaRad)
  if qiblaDeg < 0 then qiblaDeg = qiblaDeg + 360 end
  return qiblaDeg
end

function getCurrentLocationDegrees()
  local lat, lon = getUserCoordinates()
  return calculateQiblaDegree(lat, lon)
end

-- 🛠️ دالة جلب التاريخ الهجري المعززة والمصلحة بشكل نهائي لمنع رجوع نص فارغ
function getSystemHijriDate()
  local hMonths = {"محرم", "صفر", "ربيع الأول", "ربيع الثاني", "جمادى الأولى", "جمادى الآخرة", "رجب", "شعبان", "رمضان", "شوال", "ذو القعدة", "ذو الحجة"}
  local textResult = ""
  
  pcall(function()
    local uLocale = ULocale("@calendar=islamic-umalqura")
    local islamicCal = IslamicCalendar.getInstance(uLocale)
    islamicCal.setTime(java.util.Date())
    
    local hDay = islamicCal.get(Calendar.DAY_OF_MONTH)
    local hMonthIdx = islamicCal.get(Calendar.MONTH) + 1
    local hYear = islamicCal.get(Calendar.YEAR)
    
    if hDay and hMonthIdx and hYear then
      textResult = hDay .. " " .. hMonths[hMonthIdx] .. " سنة " .. hYear .. " هجرية"
    end
  end)
  
  if textResult == "" or textResult == nil then 
    pcall(function()
      local uLocaleGeneric = ULocale("@calendar=islamic")
      local islamicCalGeneric = IslamicCalendar.getInstance(uLocaleGeneric)
      islamicCalGeneric.setTime(java.util.Date())
      local hDay = islamicCalGeneric.get(Calendar.DAY_OF_MONTH)
      local hMonthIdx = islamicCalGeneric.get(Calendar.MONTH) + 1
      local hYear = islamicCalGeneric.get(Calendar.YEAR)
      textResult = hDay .. " " .. hMonths[hMonthIdx] .. " سنة " .. hYear .. " هجرية"
    end)
  end
  
  if textResult == "" or textResult == nil then 
    textResult = "متاح عبر النظام الداخلي للهاتف" 
  end
  return textResult
end

-- 🛠️ دالة جلب إعدادات سرعة محرك النطق من نظام الأندرويد لربطه بالشاشة الرئيسية
local function getSystemTtsSettings()
  local rate = 1.0
  local pitch = 1.0
  pcall(function()
    local cr = service.getContentResolver()
    rate = Settings.Secure.getFloat(cr, Settings.Secure.TTS_DEFAULT_RATE, 100) / 100
    pitch = Settings.Secure.getFloat(cr, Settings.Secure.TTS_DEFAULT_PITCH, 100) / 100
  end)
  return rate, pitch
end

-- 🛠️ دالة حساب مواقيت الصلاة جغرافيا وفلكيا بالاعتماد الكلي على خطوط الطول والعرض لضمان تغير المواعيد بين الولايات والبلديات
function getNextPrayerStatus()
  local lat, lon = getUserCoordinates()
  local cal = Calendar.getInstance()
  local year = cal.get(Calendar.YEAR)
  local month = cal.get(Calendar.MONTH) + 1
  local day = cal.get(Calendar.DAY_OF_MONTH)
  local hour = cal.get(Calendar.HOUR_OF_DAY)
  local minute = cal.get(Calendar.MINUTE)
  local dayOfYear = cal.get(Calendar.DAY_OF_YEAR)
  local timeZoneOffset = cal.get(Calendar.ZONE_OFFSET) / (1000 * 60 * 60)
  
  local arMonths = {"جانفي", "فيفري", "مارس", "أفريل", "ماي", "جوان", "جويلية", "أوت", "سبتمبر", "أكتوبر", "نوفمبر", "ديسمبر"}
  local miladiDateStr = "التاريخ الميلادي هو: " .. day .. " " .. arMonths[month] .. " سنة " .. year .. " ميلادية"
  
  local hijriDateStr = "التاريخ الهجري هو: " .. getSystemHijriDate()
  
  -- الحسابات الفلكية الدقيقة بناءً على الموقع الفعلي للمستخدم
  local d = dayOfYear
  local B = 2 * math.pi * (d - 81) / 365
  local EoT = 9.87 * math.sin(2 * B) - 7.53 * math.cos(B) - 1.5 * math.sin(B)
  
  -- تعديل دقيق لوقت الزوال (الظهر) ليتأثر فارق خط الطول الجغرافي الفعلي بين الشرق والغرب
  local noonDecimal = 12 + ((timeZoneOffset * 15 - lon) / 15) - (EoT / 60)
  
  local declination = math.rad(23.45 * math.sin(2 * math.pi * (284 + d) / 365))
  local latRad = math.rad(lat)
  
  local function hourAngle(angle)
    local angleRad = math.rad(angle)
    local num = math.sin(angleRad) - math.sin(latRad) * math.sin(declination)
    local den = math.cos(latRad) * math.cos(declination)
    local cosH = num / den
    if cosH > 1 then cosH = 1 elseif cosH < -1 then cosH = -1 end
    return math.deg(math.acos(cosH))
  end
  
  local H_fajr = hourAngle(-18.0) 
  local H_isha = hourAngle(-18.0) 
  local H_sunset = hourAngle(-0.833)
  
  local asrAngleRad = math.atan(1 + math.tan(math.abs(latRad - declination)))
  local cosH_asr = (math.sin(asrAngleRad) - math.sin(latRad) * math.sin(declination)) / (math.cos(latRad) * math.cos(declination))
  if cosH_asr > 1 then cosH_asr = 1 elseif cosH_asr < -1 then cosH_asr = -1 end
  local H_asr = math.deg(math.acos(cosH_asr))
  
  -- تحويل الساعات الكسرية إلى دقائق حقيقية للموقع الجغرافي الحالي
  local fajrTime = math.floor((noonDecimal - (H_fajr / 15)) * 60)
  local dhuhrTime = math.floor(noonDecimal * 60) + 5 -- إضافة وقت الاحتياط للزوال
  local asrTime = math.floor((noonDecimal + (H_asr / 15)) * 60)
  local maghribTime = math.floor((noonDecimal + (H_sunset / 15)) * 60)
  local ishaTime = math.floor((noonDecimal + (H_isha / 15)) * 60)
  
  local prayers = {
    {name = "الفجر", time = fajrTime},
    {name = "الظهر", time = dhuhrTime},
    {name = "العصر", time = asrTime},
    {name = "المغرب", time = maghribTime}, 
    {name = "العشاء", time = ishaTime}
  }
  
  local currentTimeInMinutes = (hour * 60) + minute
  local nextPrayer = nil
  local minutesLeft = 0
  
  for _, prayer in ipairs(prayers) do
    if prayer.time > currentTimeInMinutes then
      nextPrayer = prayer
      minutesLeft = prayer.time - currentTimeInMinutes
      break
    end
  end
  
  if not nextPrayer then
    nextPrayer = prayers[1]
    minutesLeft = (1440 - currentTimeInMinutes) + prayers[1].time
  end
  
  local leftHours = math.floor(minutesLeft / 60)
  local leftMinutes = minutesLeft % 60
  local timeString = ""
  if leftHours > 0 then timeString = leftHours .. " ساعة و " end
  timeString = timeString .. leftMinutes .. " دقيقة"
  
  local prayerHour = math.floor(nextPrayer.time / 60)
  local prayerMinute = nextPrayer.time % 60
  if prayerHour >= 24 then prayerHour = prayerHour - 24 end
  
  local period = "مساءً"
  if prayerHour < 12 then period = "صباحاً" end
  local displayHour = prayerHour % 12
  if displayHour == 0 then displayHour = 12 end
  
  local formattedPrayerTime = displayHour .. " و " .. string.format("%02d", prayerMinute) .. " دقيقة " .. period
  local finalSpeechText = miladiDateStr .. "، و " .. hijriDateStr .. ". الصلاة القادمة هي صلاة " .. nextPrayer.name .. " على الساعة " .. formattedPrayerTime .. "، والوقت المتبقي لها هو " .. timeString .. "."
  return finalSpeechText
end

function startQiblaVibratorFinder()
  _G.QiblaActive = true
  service.speak("جاري تشغيل بوصلة القبلة. ضع الهاتف مستوياً وحركه ببطء. قل توقف في أي وقت لإغلاقها")

  if not accelerometerSensor or not magneticSensor then
    task(1500, function()
      service.speak("عذراً، جهازك لا يدعم مستشعرات البوصلة الدقيقة")
    end)
    _G.QiblaActive = false
    if _G.ContinuousDictationRunning then iniciarDictado() end
    return
  end

  task(400, function()
    if recognizer then pcall(function() recognizer.destroy() end) _G.GlobalRecognizer = nil end
  end)

  local QIBLA_TARGET_DEGREE = getCurrentLocationDegrees()
  local mGravity = nil
  local mGeomagnetic = nil
  local R = Array.newInstance(Float.TYPE, 9)
  local I = Array.newInstance(Float.TYPE, 9)
  local orientation = Array.newInstance(Float.TYPE, 3)

  _G.qiblaListener = luajava.createProxy("android.hardware.SensorEventListener", {
    onSensorChanged = function(event)
      if not _G.QiblaActive then return end
      if event.sensor.getType() == Sensor.TYPE_ACCELEROMETER then
        mGravity = event.values
      elseif event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD then
        mGeomagnetic = event.values
      end
      if mGravity and mGeomagnetic then
        local success = SensorManager.getRotationMatrix(R, I, mGravity, mGeomagnetic)
        if success then
          SensorManager.getOrientation(R, orientation)
          local azimuth = math.deg(orientation[0])
          if azimuth < 0 then azimuth = azimuth + 360 end
          if math.abs(azimuth - QIBLA_TARGET_DEGREE) <= 4 then
            vibrator.vibrate(250) 
          end
        end
      end
    end,
    onAccuracyChanged = function(sensor, accuracy) end
  })

  _G.sensorManager.registerListener(_G.qiblaListener, accelerometerSensor, SensorManager.SENSOR_DELAY_UI)
  _G.sensorManager.registerListener(_G.qiblaListener, magneticSensor, SensorManager.SENSOR_DELAY_UI)

  task(1500, function()
    if not _G.QiblaActive then return end
    pcall(function()
      local qiblaRecognizer = SpeechRecognizer.createSpeechRecognizer(service)
      local qiblaIntent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH)
      qiblaIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, "ar-SA")
      
      local qiblaSpeechListener = {
        onResults = function(results)
          local data = results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION)
          if data and not data.isEmpty() then
            local text = tostring(data.get(0)):lower():trim()
            if text == "توقف" or text == "إنهاء" or text == "توقف البوصلة" then
              _G.QiblaActive = false
              if _G.sensorManager and _G.qiblaListener then
                _G.sensorManager.unregisterListener(_G.qiblaListener)
                _G.qiblaListener = nil
              end
              pcall(function() qiblaRecognizer.destroy() end)
              service.speak("تم إيقاف البوصلة يدوياً بنجاح")
              if _G.ContinuousDictationRunning then iniciarDictado() end
              return
            end
          end
          if _G.QiblaActive then pcall(function() qiblaRecognizer.startListening(qiblaIntent) end) end
        end,
        onError = function()
          if _G.QiblaActive then pcall(function() qiblaRecognizer.startListening(qiblaIntent) end) end
        end,
        onReadyForSpeech = function() end
      }
      qiblaRecognizer.setRecognitionListener(luajava.createProxy("android.speech.RecognitionListener", qiblaSpeechListener))
      qiblaRecognizer.startListening(qiblaIntent)
    end)
  end)

  task(25000, function()
    if _G.QiblaActive and _G.qiblaListener then
      _G.QiblaActive = false
      _G.sensorManager.unregisterListener(_G.qiblaListener)
      _G.qiblaListener = nil
      service.speak("تم إغلاق بوصلة القبلة تلقائياً")
      if _G.ContinuousDictationRunning then iniciarDictado() end
    end
  end)
end

function translateAndInsert(text, targetLang, edit, currentLang)
  Thread(Runnable({
    run = function()
      local finalOutput = text
      pcall(function()
        local urlStr = "https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=" .. targetLang .. "&dt=t&q=" .. URLEncoder.encode(text, "UTF-8")
        local url = URL(urlStr)
        local conn = url.openConnection()
        conn.setRequestMethod("GET")
        conn.setRequestProperty("User-Agent", "Mozilla/5.0")
        conn.setConnectTimeout(4000)
        conn.setReadTimeout(4000)
        
        if conn.getResponseCode() == 200 then
          local reader = BufferedReader(InputStreamReader(conn.getInputStream(), "UTF-8"))
          local response = StringBuilder()
          local line = reader.readLine()
          while line ~= nil do
            response.append(line)
            line = reader.readLine()
          end
          reader.close()
          local rawJson = tostring(response)
          local translatedStr = rawJson:match("^%[%[%[\"(.-)\",\"")
          if translatedStr and translatedStr ~= "" then finalOutput = translatedStr end
        end
      end)
      
      Handler(Looper.getMainLooper()).post(Runnable({
        run = function()
          if edit then service.insertText(edit, processTextEfficiency(finalOutput, currentLang)) end
          if _G.ContinuousDictationRunning then iniciarDictado() end
        end
      }))
    end
  })).start()
end

function hybridDiacritize(edit, textToDiacritize)
  if not edit then 
    service.speak("مربع التعديل غير موجود")
    if _G.ContinuousDictationRunning then iniciarDictado() end
    return 
  end
  local text = tostring(edit.getText()):trim()
  if textToDiacritize and textToDiacritize ~= "" then
    if text ~= "" then text = text .. " " .. textToDiacritize else text = textToDiacritize end
  end
  if text == "" then 
    service.speak("مربع التعديل فارغ")
    if _G.ContinuousDictationRunning then iniciarDictado() end
    return 
  end
  
  if recognizer then pcall(function() recognizer.stopListening() end) end
  local resultText = text
  local sortedKeys = {}
  for k in pairs(localTashkeelMap) do table.insert(sortedKeys, k) end
  table.sort(sortedKeys, function(a, b) return utf8.len(a) > utf8.len(b) end)
  
  for _, key in ipairs(sortedKeys) do
    if resultText:find(key) then resultText = resultText:gsub(key, localTashkeelMap[key]) end
  end
  
  if resultText != text and not resultText:match("[أ-ي😂🦁🍞🕌⚠️⚽]") then
    service.setText(edit, resultText .. " ")
    service.speak("تم التشكيل الديني الفوري بنجاح")
    if _G.ContinuousDictationRunning then iniciarDictado() end
    return
  end
  
  service.speak("جاري إكمال تشكيل النص ديني وعام")
  Thread(Runnable({
    run = function()
      local diacritizedText = resultText
      pcall(function()
        local url = URL("https://tashkeel.mishkal.org/ajax/get_tashkeel")
        local conn = url.openConnection()
        conn.setRequestMethod("POST")
        conn.setRequestProperty("User-Agent", "Mozilla/5.0")
        conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
        conn.setConnectTimeout(6000)
        conn.setReadTimeout(6000)
        conn.setDoOutput(true)
        
        local postData = "text=" .. URLEncoder.encode(text, "UTF-8")
        local os = conn.getOutputStream()
        os.write(String(postData).getBytes("UTF-8"))
        os.flush()
        os.close()
        
        if conn.getResponseCode() == 200 then
          local reader = BufferedReader(InputStreamReader(conn.getInputStream(), "UTF-8"))
          local response = StringBuilder()
          local line = reader.readLine()
          while line ~= nil do
            response.append(line)
            line = reader.readLine()
          end
          reader.close()
          local rawJson = tostring(response)
          local output = rawJson:match("\"output\":\"(.-)\"")
          if output and output ~= "" then
            local serverOutput = decodeUnicode(output)
            serverOutput = serverOutput:gsub("\\n", "\n"):gsub("\\\"", "\""):gsub("\\\\", "\\")
            for _, key in ipairs(sortedKeys) do
              local cleanKey = localTashkeelMap[key]
              local escapedKey = key:gsub("([%^%$%%%.%*%+%-%?%[%]%(%)])", "%%%1")
              serverOutput = serverOutput:gsub(escapedKey, cleanKey)
            end
            diacritizedText = serverOutput
          end
        end
      end)
      
      Handler(Looper.getMainLooper()).post(Runnable({
        run = function()
          if diacritizedText ~= text and diacritizedText ~= "" then
            service.setText(edit, diacritizedText .. " ")
            service.speak("تم تشكيل النص بالكامل")
          else
            service.setText(edit, resultText .. " ")
            service.speak("تم التشكيل المحلي المتاح للأذكار والتحايا")
          end
          if _G.ContinuousDictationRunning then iniciarDictado() end
        end
      }))
    end
  })).start()
end

function showHelpDialog()
  _G.ContinuousDictationRunning = false
  if _G.GlobalRecognizer then pcall(function() _G.GlobalRecognizer.destroy() end) _G.GlobalRecognizer = nil end
  Handler(Looper.getMainLooper()).post(Runnable({
    run = function()
      local wm = service.getSystemService(Context.WINDOW_SERVICE)
      local layout = LinearLayout(service)
      layout.setOrientation(LinearLayout.VERTICAL)
      layout.setPadding(50, 50, 50, 50)
      layout.setBackgroundColor(Color.parseColor("#1A1A1A"))
      
      local title = TextView(service)
      title.setText("الإملاء المستمر بدون مفتاح (API).")
      title.setTextSize(22)
      title.setTextColor(Color.WHITE)
      title.setTypeface(Typeface.DEFAULT_BOLD)
      title.setGravity(Gravity.CENTER)
      title.setPadding(0, 10, 0, 10)
      layout.addView(title)
      
      local dev = TextView(service)
      dev.setText("تم تطويرها بواسطة سايح فتحي")
      dev.setTextSize(16)
      dev.setTextColor(Color.parseColor("#00FFFF"))
      dev.setGravity(Gravity.CENTER)
      dev.setPadding(0, 0, 0, 5)
      layout.addView(dev)
      
      local dua = TextView(service)
      dua.setText("اللهم اغفر لوالدي وارحمهما وعافهما واعف عنهما، اللهم جازهما بالإحسان إحساناً وبالسيئات غفراناً ورضواناً")
      dua.setTextSize(16)
      dua.setTextColor(Color.parseColor("#FFD700"))
      dua.setGravity(Gravity.CENTER)
      dua.setPadding(0, 5, 0, 20)
      layout.addView(dua)

      local scroll = ScrollView(service)
      local contentLayout = LinearLayout(service)
      contentLayout.setOrientation(LinearLayout.VERTICAL)
      
      local commandsList = {
        { "توقف أو Stop أو Arrête : ", "لإغلاق المايكروفون وإيقاف الإضافة نهائياً." },
        { "توقف قليلاً أو Pause : ", "لعمل استراحة مؤقتة للإملاء مدتها 5 ثوانٍ ثم يعود تلقائياً." },
        { "إرسال أو Send أو Envoyer : ", "لإرسال النص داخل تطبيقات المراسلة فوراً وبشكل تلقائي." },
        { "تراجع أو Undo أو Annuler : ", "لحذف آخر جملة أو عبارة قمت بنطقها داخل مربع التعديل." },
        { "إيموجي أو Emoji : ", "لإضافة الرموز التعبيرية والملصقات الذكية المناسبة للنص بـ 3 لغات." },
        { "سطر جديد أو New line أو Nouvelle ligne : ", "للانتقال للسطر الأسفل داخل مربع الكتابة مباشرة." },
        { "تشكيل أو تشكيل النص أو Vocalisation : ", "لسحب كامل النص الموجود في المربع وتشكيله دينياً وعاماً." },
        { "مراجعة أو Review أو Révision : ", "لنطق النص بالكامل متبوعاً بوقفة دقيقة مدتها 3 ثوانٍ قبل استئناف المايك." },
        { "بوصلة أو البوصلة أو Boussole : ", "لتحديد اتجاه الكعبة عالمياً عبر الاهتزاز الحركي الفوري. قل (توقف) لإغلاقها." },
        { "الصلاة القادمة أو صلاة قادمة أو Prière : ", "لنطق التاريخ الميلادي والهجري، والصلاة القادمة بدقة والوقت المتبقي لها." },
        { "الاملاء بالفرنسية / بالإنجليزية / بالعربية : ", "لتحويل لغة التعرف على الكلام الفورية إلى اللغة المحددة مباشرة." },
        { "الترجمة بالفرنسية / بالإنجليزية / بالعربية : ", "لتفعيل المترجم الفوري الشامل؛ حيث تتحدث ويقوم بالإدخال مترجماً للغة الهدف." }
      }

      for _, cmd in ipairs(commandsList) do
        local rowTextView = TextView(service)
        rowTextView.setText(cmd[1] .. cmd[2])
        rowTextView.setTextSize(15)
        rowTextView.setTextColor(Color.WHITE)
        rowTextView.setPadding(10, 12, 10, 12)
        rowTextView.setFocusable(true)
        contentLayout.addView(rowTextView)
      end
      
      scroll.addView(contentLayout)
      layout.addView(scroll, LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1.0))
      
      local closeBtn = Button(service)
      closeBtn.setText("إغلاق وتفعيل المايك")
      closeBtn.setTextColor(Color.WHITE)
      closeBtn.setBackgroundColor(Color.parseColor("#FF0000"))
      layout.addView(closeBtn)
      
      local params = WindowManager.LayoutParams()
      params.width = WindowManager.LayoutParams.MATCH_PARENT
      params.height = WindowManager.LayoutParams.MATCH_PARENT
      params.type = WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY
      wm.addView(layout, params)
      
      closeBtn.setOnClickListener(View.OnClickListener({
        onClick = function(v)
          wm.removeView(layout)
          _G.ContinuousDictationRunning = true
          iniciarDictado()
        end
      }))
    end
  }))
end

function applyEmojisWithCount(text, count)
  local result = text:trim()
  if result == "" then return result end
  local sortedKeys = {}
  for k in pairs(emojiMap) do table.insert(sortedKeys, k) end
  table.sort(sortedKeys, function(a, b) return utf8.len(a) > utf8.len(b) end)
  for _, word in ipairs(sortedKeys) do
    local emoji = emojiMap[word]
    local finalEmoji = ""
    for i=1, count do finalEmoji = finalEmoji .. emoji end
    if result:lower():find(word) then result = result:gsub(word, word .. " " .. finalEmoji) end
  end
  return result
end

function undoLastSentence(edit)
  local txt = tostring(edit.getText()):trim()
  if txt == "" then return end
  local lastPuncIdx = 0
  local puncs = {"%.", "؟", "!", "%?"}
  for _, p in ipairs(puncs) do
    local s, e = txt:find("[^"..p.."]*"..p.."%s*$")
    if s and s > lastPuncIdx then lastPuncIdx = s end
  end
  if lastPuncIdx > 0 then
    service.setText(edit, txt:sub(1, lastPuncIdx):trim() .. " ")
  else
    service.setText(edit, "")
  end
  service.speak(_G.DictationLang == "ar-SA" and "تم التراجع" or (_G.DictationLang == "fr-FR" and "Annulé" or "Undo"))
end

function makeCall(target)
  target = target:trim()
  if target == "" then return false end
  if target:match("^%d+$") then
    local intent = Intent(Intent.ACTION_CALL, Uri.parse("tel:" .. target))
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
    service.startActivity(intent)
    service.speak(_G.DictationLang == "ar-SA" and ("جاري الاتصال بالرقم " .. target) or ("Appel du " .. target))
    return true
  else
    local uri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI
    local projection = {ContactsContract.CommonDataKinds.Phone.NUMBER, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME}
    local cursor = service.getContentResolver().query(uri, projection, nil, nil, nil)
    if cursor then
      while cursor.moveToNext() do
        local name = tostring(cursor.getString(1))
        local number = tostring(cursor.getString(0))
        if name:lower():trim() == target:lower() or name:lower():trim():find(target:lower(), 1, true) then
          local intent = Intent(Intent.ACTION_CALL, Uri.parse("tel:" .. number))
          intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
          service.startActivity(intent)
          service.speak(_G.DictationLang == "ar-SA" and ("جاري الاتصال بـ " .. name) or ("Appel de " .. name))
          cursor.close()
          return true
        end
      end
      cursor.close()
    end
    service.speak(_G.DictationLang == "ar-SA" and "لم أجد هذا الاسم في جهات الاتصال" or "Contact introuvable")
  end
  return false
end

function fixHamza(text)
  if _G.DictationLang ~= "ar-SA" and _G.TargetTranslationLang ~= "ar" then return text end
  local words = {}
  for word in text:gmatch("%S+") do
    local newWord = word
    if newWord == "الجزائر" or newWord == "بالجزائر" or newWord == "والجزائر" or newWord == "فالجزائر" then
      newWord = newWord:gsub("الاجزائر", "الجزائر"):gsub("الجزأير", "الجزائر")
    elseif newWord == "الا" or newWord == "إلا" then
      newWord = "إلا"
    else
      newWord = newWord:gsub("فاس", "فأس")
      newWord = newWord:gsub("ياس", "يأس")
      newWord = newWord:gsub("ياخذ", "يأخذ")
      newWord = newWord:gsub("ياكل", "يأكل")
      newWord = newWord:gsub("كاس", "كأس")
      newWord = newWord:gsub("فار", "فأر")
      newWord = newWord:gsub("مساله", "مسألة")
      newWord = newWord:gsub("فجاه", "فجأة")
      newWord = newWord:gsub("نشاه", "نشأة")
      newWord = newWord:gsub("يييس", "يئس")
      
      newWord = newWord:gsub("([^%s])ا([^%s])", function(before, after)
        if before:match("[يتمسر فكنتظغب حجخعقثصض]") and not after:match("[ويامها]") then 
          local current = before .. "أ" .. after
          if current:find("فأس") or current:find("يأس") or current:find("يأخذ") or current:find("يأكل") or current:find("كأس") or current:find("فأر") then
            return before .. "ا" .. after
          end
          return current
        end
        return before .. "ا" .. after
      end)
      
      if newWord:find("ا$") and not newWord:find("^ال") and utf8.len(newWord) > 2 then
        local endsWithHamzaPattern = {"قرا", "بدا", "نشا", "ملا", "خطا", "لجا"}
        for _, pattern in ipairs(endsWithHamzaPattern) do
          if newWord:find(pattern .. "$") then newWord = newWord:gsub("ا$", "أ") break end
        end
      end
      
      local prefix = ""
      local patterns = {"^بالال", "^والال", "^الال", "^بال", "^وال", "^ال", "^لل"}
      for _, p in ipairs(patterns) do
        if newWord:find(p) and utf8.len(newWord) > (utf8.len(p) - 1) then prefix = newWord:match(p) newWord = newWord:gsub(p, "") break end
      end
      if newWord:find("^ا") and not newWord:find("^است") and newWord ~= "اسم" then
        if newWord:find("^اي") or newWord:find("^إي") or newWord:find("^اذ") or newWord == "الى" or newWord == "إلى" or newWord == "ان" or newWord == "إن" or newWord == "العافية" then
          newWord = newWord:gsub("^ا", "إ")
        else
          newWord = newWord:gsub("^ا", "أ")
        end
      end
      newWord = prefix .. newWord
      newWord = newWord:gsub("الاسلام", "الإسلام"):gsub("بالاسلام", "بالإسلام")
    end
    table.insert(words, newWord)
  end
  return table.concat(words, " ")
end

function fixMarbuta(text)
  if _G.DictationLang ~= "ar-SA" and _G.TargetTranslationLang ~= "ar" then return text end
  local strictly_h = {"الله", "بالله", "والله", "لله", "إله", "وجه", "هذه", "بركاته", "وبركاته", "رحمه"}
  local strictly_t = {"كرة", "الكرة", "العافية"}
  local words = {}
  for word in text:gmatch("%S+") do
    local processed = false
    for _, t in ipairs(strictly_t) do
      if word:gsub("ة", "ه") == t:gsub("ة", "ه") then word = t processed = true break end
    end
    if not processed then
      local skip = false
      for _, h in ipairs(strictly_h) do
        if word:gsub("ة", "ه") == h then word = h skip = true break end
      end
      if not skip and utf8.len(word) > 3 then word = word:gsub("ه$", "ة") end
    end
    table.insert(words, word)
  end
  return table.concat(words, " ")
end

function processTextEfficiency(text, currentLang)
  if not text or text == "" then return "" end
  if currentLang == "ar-SA" or _G.TargetTranslationLang == "ar" then
    text = text:gsub(",", "،"):gsub("?", "؟")
    text = fixHamza(text)
    text = text:gsub(" Allah", " الله") 
    text = fixMarbuta(text)
  end
  text = text:trim()
  local punc = "."
  if currentLang == "ar-SA" or _G.TargetTranslationLang == "ar" then
    if text:find("^هل ") or text:find("^كيف ") or text:find("^متى ") then punc = "؟" end
  else
    if text:find("^why") or text:find("^how") or text:find("^what") or text:find("^pourquoi") or text:find("^est-ce") then punc = "?" end
  end
  if not text:match("[%.%?%!؟]$") then text = text .. punc end
  return text .. " "
end

function openAppByName(name)
  local pm = service.getPackageManager()
  local targetName = name:lower():trim()
  
  local translatedPackage = appTranslationMap[targetName]
  if translatedPackage then 
    local directIntent = pm.getLaunchIntentForPackage(translatedPackage)
    if directIntent then
      directIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
      service.speak(_G.DictationLang == "ar-SA" and "جاري الفتح" or "Opening app")
      service.startActivity(directIntent)
      return true
    end
  end
  
  local directIntent = pm.getLaunchIntentForPackage(targetName)
  if directIntent then
    directIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
    service.speak(_G.DictationLang == "ar-SA" and "جاري الفتح" or "Opening app")
    service.startActivity(directIntent)
    return true
  end

  local apps = pm.getInstalledApplications(0)
  for i = 0, apps.size() - 1 do
    local app = apps.get(i)
    local label = tostring(pm.getApplicationLabel(app)):lower():trim()
    local packageName = tostring(app.packageName):lower()
    
    if targetName == "messenger" or targetName == "ماسنحر" or targetName == "مسنجر" then
      if packageName == "com.facebook.orca" then
        local intent = pm.getLaunchIntentForPackage(app.packageName)
        if intent then
          intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
          service.speak(_G.DictationLang == "ar-SA" and "جاري فتح مسنجر" or "Opening Messenger")
          service.startActivity(intent)
          return true
        end
      end
    else
      if label:find(targetName, 1, true) or packageName:find(targetName, 1, true) then
        local intent = pm.getLaunchIntentForPackage(app.packageName)
        if intent then
          intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
          service.speak(_G.DictationLang == "ar-SA" and ("جاري فتح " .. tostring(pm.getApplicationLabel(app))) or "Opening " .. label)
          service.startActivity(intent)
          return true
        end
      end
    end
  end
  
  service.speak(_G.DictationLang == "ar-SA" and "تعذر العثور على التطبيق المطلوب" or "App not found")
  return false
end

function sendAction()
  local root = service.getRootInActiveWindow()
  if not root then return false end
  
  local sendIds = {
    "org.telegram.messenger:id/chat_send_button",
    "com.whatsapp:id/send",
    "com.facebook.orca:id/text_send_button",
    "com.facebook.katana:id/send_button",
    "com.instagram.android:id/row_thread_button_send",
    "com.viber.voip:id/send_button",
    "com.google.android.apps.messaging:id/send_message_button_icon"
  }
  
  for _, id in ipairs(sendIds) do
    local nodes = root.findAccessibilityNodeInfosByViewId(id)
    if nodes and not nodes.isEmpty() then
      for i = 0, nodes.size() - 1 do
        local node = nodes.get(i)
        if node.isClickable() then
          node.performAction(AccessibilityNodeInfo.ACTION_CLICK)
          return true
        elseif node.getParent() and node.getParent().isClickable() then
          node.getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK)
          return true
        end
      end
    end
  end
  
  local descKeywords = {
    "إرسال", "ارسل", "انشر", "بحث", "تم",
    "send", "submit", "publish", "search", "done",
    "envoyer", "envoie", "envoyé"
  }
  
  for _, desc in ipairs(descKeywords) do
    local queue = {root}
    while #queue > 0 do
      local current = table.remove(queue, 1)
      if current then
        local contentDesc = current.getContentDescription()
        if contentDesc and tostring(contentDesc):lower():find(desc) then
          if current.isClickable() then
            current.performAction(AccessibilityNodeInfo.ACTION_CLICK)
            return true
          elseif current.getParent() and current.getParent().isClickable() then
            current.getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK)
            return true
          end
        end
        for i = 0, current.getChildCount() - 1 do
          table.insert(queue, current.getChild(i))
        end
      end
    end
  end

  for _, key in ipairs(descKeywords) do
    local btns = root.findAccessibilityNodeInfosByText(key)
    if btns and not btns.isEmpty() then
      for i = 0, btns.size() - 1 do
        local btn = btns.get(i)
        if btn.isClickable() then
          btn.performAction(AccessibilityNodeInfo.ACTION_CLICK)
          return true
        elseif btn.getParent() and btn.getParent().isClickable() then
          btn.getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK)
          return true
        end
      end
    end
  end
  return false
end

function performReviewAction(edit)
  local rawFullText = edit.getText()
  local fullText = rawFullText and tostring(rawFullText):trim() or ""
  
  if fullText == "" or fullText == "nil" then
    service.speak(_G.DictationLang == "ar-SA" and "مربع التعديل فارغ" or "Field is empty")
    task(1000, function() if _G.ContinuousDictationRunning then iniciarDictado() end end)
    return
  end

  if recognizer then pcall(function() recognizer.stopListening() end) end

  Handler(Looper.getMainLooper()).postDelayed(Runnable({
    run = function()
      if internalTts then pcall(function() internalTts.shutdown() end) internalTts = nil end
      
      internalTts = TextToSpeech(service, luajava.createProxy("android.speech.tts.TextToSpeech$OnInitListener", {
        onInit = function(status)
          if status == TextToSpeech.SUCCESS then
            local currentLocale = Locale("ar", "SA")
            if _G.DictationLang == "fr-FR" then currentLocale = Locale.FRANCE
            elseif _G.DictationLang == "en-US" then currentLocale = Locale.US end
            internalTts.setLanguage(currentLocale)

            -- 🔄 ضبط سرعة الصوت والحدة تلقائياً لتطابق الشاشة الرئيسية للنظام تماماً
            local systemRate, systemPitch = getSystemTtsSettings()
            internalTts.setSpeechRate(systemRate)
            internalTts.setPitch(systemPitch)

            pcall(function()
              local progressListener = luajava.new(luajava.bindClass("android.speech.tts.UtteranceProgressListener"), {
                onStart = function(utteranceId) end,
                onDone = function(utteranceId)
                  Handler(Looper.getMainLooper()).post(Runnable({
                    run = function()
                      playSystemBeep()
                      if internalTts then pcall(function() internalTts.shutdown() end) internalTts = nil end
                      task(400, function()
                        if _G.ContinuousDictationRunning then iniciarDictado() end
                      end)
                    end
                  }))
                end,
                onError = function(utteranceId)
                  Handler(Looper.getMainLooper()).post(Runnable({
                    run = function()
                      if _G.ContinuousDictationRunning then iniciarDictado() end
                    end
                  }))
                end
              })
              internalTts.setOnUtteranceProgressListener(progressListener)
            end)

            local params = HashMap()
            params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "ReviewReviewSayah")
            internalTts.speak(fullText, TextToSpeech.QUEUE_FLUSH, params)
          else
            service.speak(fullText)
            local _, wordCount = fullText:gsub("%S+", "")
            local fallbackDuration = math.max(3500, (wordCount * 350) + 1500)
            task(fallbackDuration, function()
              playSystemBeep()
              if _G.ContinuousDictationRunning then iniciarDictado() end
            end)
          end
        end
      }))
    end
  }), 200)
end

function iniciarDictado()
  if not _G.ContinuousDictationRunning then return end
  if recognizer then pcall(function() recognizer.destroy() end) end
  pcall(function()
    recognizer = SpeechRecognizer.createSpeechRecognizer(service)
    _G.GlobalRecognizer = recognizer
    local intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH)
    intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, _G.DictationLang)
    
    local listener = {
      onResults = function(results)
        local data = results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION)
        if data and not data.isEmpty() then
          local rawText = tostring(data.get(0)):trim()
          local lower = rawText:lower()
          local edit = service.getEditText()
          local endsWithTashkeel = false
          local cleanSpeechText = rawText
          
          if _G.DictationLang == "ar-SA" then
            if lower:match("^تشكيل النص") or lower:match("^تشكيل") then
              endsWithTashkeel = true
              cleanSpeechText = rawText:gsub("^%s*تشكيل النص%s*", ""):gsub("^%s*تشكيل%s*", ""):trim()
            elseif lower:match("تشكيل النص$") or lower:match("تشكيل$") then
              endsWithTashkeel = true
              cleanSpeechText = rawText:gsub("%s*تشكيل النص$", ""):gsub("%s*تشكيل$", ""):trim()
            end
          elseif _G.DictationLang == "fr-FR" then
            if lower:match("^vocalisation") or lower:match("^vocaliser") then
              endsWithTashkeel = true
              cleanSpeechText = rawText:gsub("^%s*vocalisation%s*", ""):gsub("^%s*vocaliser%s*", ""):trim()
            elseif lower:match("vocalisation$") or lower:match("vocaliser$") then
              endsWithTashkeel = true
              cleanSpeechText = rawText:gsub("%s*vocalisation$", ""):gsub("%s*vocaliser$", ""):trim()
            end
          elseif _G.DictationLang == "en-US" then
            if lower:match("^diacritize") then
              endsWithTashkeel = true
              cleanSpeechText = rawText:gsub("^%s*diacritize%s*", ""):trim()
            elseif lower:match("diacritize$") then
              endsWithTashkeel = true
              cleanSpeechText = rawText:gsub("%s*diacritize$", ""):trim()
            end
          end

          if endsWithTashkeel and edit then
            hybridDiacritize(edit, cleanSpeechText)
            return
          end

          local cmdToFrench, cmdToEnglish, cmdToArabic = false, false, false
          local cmdTransFrench, cmdTransEnglish, cmdTransArabic = false, false, false
          local cmdStop, cmdSend, cmdPause, cmdUndo, cmdEmoji, cmdReview, cmdDelete, cmdNewLine, cmdTashkeel = false, false, false, false, false, false, false, false, false
          local cmdBousla, cmdSalat = false, false

          if _G.DictationLang == "ar-SA" then
            cmdToFrench   = (lower == "الاملاء بالفرنسيه" or lower == "الاملاء بالفرنسية")
            cmdToEnglish  = (lower == "الاملاء بالانجليزيه" or lower == "الاملاء بالإنجليزية")
            cmdTransFrench= (lower == "الترجمة بالفرنسية" or lower == "الترجمة بالفرنسيه" or lower == "الترجمة الرئيسية" or lower == "الترجمه الفرنسيه")
            cmdTransEnglish= (lower == "الترجمة بالإنجليزية" or lower == "الترجمة بالانجليزيه" or lower == "الترجمه الانجليزيه")
            cmdTransArabic = (lower == "الترجمة بالعربية" or lower == "الترجمة بالعربيه" or lower == "الترجمه العربيه")
            cmdStop       = (lower == "توقف" or lower == "إنهاء")
            cmdSend       = (lower == "إرسال" or lower == "ارسال")
            cmdPause      = (lower == "توقف قليلاً" or lower == "توقف قليلا")
            cmdUndo       = (lower == "تراجع")
            cmdEmoji      = (lower == "إيموجي" or lower == "ايموجي")
            cmdReview     = (lower == "مراجعة" or lower == "مراجعه")
            cmdDelete     = (lower == "حذف")
            cmdNewLine    = (lower == "سطر جديد")
            cmdTashkeel   = (lower == "تشكيل" or lower == "تشكيل النص")
            cmdBousla     = (lower == "بوصلة" or lower == "البوصلة" or lower:find("بوصل") or lower:find("بصل"))
            cmdSalat      = (lower == "الصلاة القادمة" or lower == "الصلاه القادمه" or lower == "صلاة قادمة")
            
          elseif _G.DictationLang == "fr-FR" then
            cmdToArabic   = (lower == "الاملاء بالعربيه" or lower == "الاملاء بالفرنسية" or lower:find("arabe"))
            cmdToEnglish  = (lower:find("anglais") or lower:find("english"))
            cmdTransFrench= (lower:find("traduction en français"))
            cmdTransEnglish= (lower:find("traduction en anglais"))
            cmdTransArabic = (lower:find("traduction en arabe"))
            cmdStop       = (lower == "arrête" or lower == "arrete" or lower == "stop" or lower:find("^arrête") or lower:find("^arrete") or lower:find("arrête%-toi") or lower:find("arrete%-toi"))
            cmdSend       = (lower == "envoyer" or lower == "envoie")
            cmdPause      = (lower == "pause")
            cmdUndo       = (lower == "annuler")
            cmdEmoji      = (lower == "emoji")
            cmdReview     = (lower == "révision" or lower == "réviser")
            cmdDelete     = (lower == "supprimer")
            cmdNewLine    = (lower == "nouvelle ligne")
            cmdTashkeel   = (lower == "vocalisation")
            cmdBousla     = (lower == "boussole" or lower == "compass" or lower:find("bouss"))
            cmdSalat      = (lower:find("prière") or lower:find("salat"))

          elseif _G.DictationLang == "en-US" then
            cmdToArabic   = (lower:find("arabic"))
            cmdToFrench   = (lower:find("french"))
            cmdTransFrench= (lower:find("translate to french"))
            cmdTransEnglish= (lower:find("translate to english"))
            cmdTransArabic = (lower:find("translate to arabic"))
            cmdStop       = (lower == "stop")
            cmdSend       = (lower == "send")
            cmdPause      = (lower == "pause")
            cmdUndo       = (lower == "undo")
            cmdEmoji      = (lower == "emoji")
            cmdReview     = (lower == "review")
            cmdDelete     = (lower == "delete")
            cmdNewLine    = (lower == "new line")
            cmdTashkeel   = (lower == "diacritize")
            cmdBousla     = (lower == "compass" or lower == "bousla" or lower:find("compas"))
            cmdSalat      = (lower:find("prayer") or lower:find("salat"))
          end

          if cmdToFrench then
            _G.TranslationMode = false
            _G.DictationLang = "fr-FR"
            service.speak("تم التحويل للإملاء الفرنسي")
            task(400, function() iniciarDictado() end)
            return
          elseif cmdToEnglish then
            _G.TranslationMode = false
            _G.DictationLang = "en-US"
            service.speak("Switching to English Dictation")
            task(400, function() iniciarDictado() end)
            return
          elseif cmdToArabic then
            _G.TranslationMode = false
            _G.DictationLang = "ar-SA"
            service.speak("تمت العودة للإملاء العربي")
            task(400, function() iniciarDictado() end)
            return
          end

          if cmdTransFrench then
            _G.TranslationMode = true
            _G.TargetTranslationLang = "fr"
            service.speak("وضع الترجمة إلى الفرنسية نشط الآن")
            task(400, function() iniciarDictado() end)
            return
          elseif cmdTransEnglish then
            _G.TranslationMode = true
            _G.TargetTranslationLang = "en"
            service.speak("وضع الترجمة إلى الإنجليزية نشط الآن")
            task(400, function() iniciarDictado() end)
            return
          elseif cmdTransArabic then
            _G.TranslationMode = true
            _G.TargetTranslationLang = "ar"
            service.speak("وضع الترجمة إلى العربية نشط الآن")
            task(400, function() iniciarDictado() end)
            return
          end

          if cmdStop then 
            _G.ContinuousDictationRunning = false 
            if _G.GlobalRecognizer then pcall(function() _G.GlobalRecognizer.destroy() end) _G.GlobalRecognizer = nil end
            service.speak(_G.DictationLang == "fr-FR" and "Arrêté" or (_G.DictationLang == "ar-SA" and "تم الإيقاف" or "Stopped")) 
            return
          elseif cmdSend then
            if sendAction() then end
            task(100, function() if _G.ContinuousDictationRunning then iniciarDictado() end end)
            return
          end

          if cmdSalat then
            if recognizer then 
              pcall(function() recognizer.destroy() end) 
              _G.GlobalRecognizer = nil 
            end
            
            local prayerStatusMessage = getNextPrayerStatus()
            
            Handler(Looper.getMainLooper()).postDelayed(Runnable({
              run = function()
                if internalTts then pcall(function() internalTts.shutdown() end) internalTts = nil end
                
                internalTts = TextToSpeech(service, luajava.createProxy("android.speech.tts.TextToSpeech$OnInitListener", {
                  onInit = function(status)
                    if status == TextToSpeech.SUCCESS then
                      internalTts.setLanguage(Locale("ar", "SA"))
                      
                      -- 🔄 جلب وتطبيق نفس إعدادات سرعة الصوت للنظام لمواقيت الصلاة أيضاً
                      local systemRate, systemPitch = getSystemTtsSettings()
                      internalTts.setSpeechRate(systemRate)
                      internalTts.setPitch(systemPitch)
                      
                      pcall(function()
                        local progressListener = luajava.new(luajava.bindClass("android.speech.tts.UtteranceProgressListener"), {
                          onStart = function(utteranceId) end,
                          onDone = function(utteranceId)
                            Handler(Looper.getMainLooper()).post(Runnable({
                              run = function()
                                playSystemBeep()
                                if internalTts then pcall(function() internalTts.shutdown() end) internalTts = nil end
                                task(400, function()
                                  if _G.ContinuousDictationRunning then iniciarDictado() end
                                end)
                              end
                            }))
                          end,
                          onError = function(utteranceId)
                            Handler(Looper.getMainLooper()).post(Runnable({
                              run = function()
                                if _G.ContinuousDictationRunning then iniciarDictado() end
                              end
                            }))
                          end
                        })
                        internalTts.setOnUtteranceProgressListener(progressListener)
                      end)
                      
                      local params = HashMap()
                      params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "PrayerTimeSayah")
                      internalTts.speak(prayerStatusMessage, TextToSpeech.QUEUE_FLUSH, params)
                    else
                      service.speak(prayerStatusMessage)
                      task(9000, function()
                        playSystemBeep()
                        if _G.ContinuousDictationRunning then iniciarDictado() end
                      end)
                    end
                  end
                }))
              end
            }), 150)
            return
          end

          if cmdBousla then
            startQiblaVibratorFinder()
            return
          end

          if _G.DictationLang == "ar-SA" then
            if appTranslationMap[lower] then
              if openAppByName(rawText) then return end
            elseif lower:find("^فتح ") then
              if openAppByName(rawText:gsub("^فتح ", "")) then return end
            elseif lower:find("^اتصال ") then
              if makeCall(rawText:gsub("^اتصال بـ ", ""):gsub("^اتصال ", "")) then return end
            end
          else
            if appTranslationMap[lower] or lower == "youtube" or lower == "facebook" then
              if openAppByName(rawText) then return end
            elseif lower:find("^open ") then
              if openAppByName(rawText:gsub("^open ", "")) then return end
            elseif lower:find("^appelle ") or lower:find("^call ") then
              if makeCall(rawText:gsub("^appelle ", ""):gsub("^call ", "")) then return end
            end
          end

          if lower == "مساعدة" or lower == "مساعده" or lower == "aide" or lower == "help" then
            showHelpDialog()
            return
          end

          if edit then
            if cmdTashkeel then
              hybridDiacritize(edit, nil)
              return 
            elseif cmdEmoji then
              service.setText(edit, applyEmojisWithCount(tostring(edit.getText()), 1))
            elseif cmdUndo then 
              undoLastSentence(edit)
            elseif cmdNewLine then 
              service.insertText(edit, "\n")
            elseif cmdDelete then 
              service.setText(edit, "")
            elseif cmdPause then
              service.speak(_G.DictationLang == "ar-SA" and "استراحة مؤقتة" or "Pause")
              task(5000, function() if _G.ContinuousDictationRunning then iniciarDictado() end end)
              return
            elseif cmdReview then
              performReviewAction(edit)
              return
            else
              if _G.TranslationMode then
                translateAndInsert(rawText, _G.TargetTranslationLang, edit, _G.DictationLang)
                return 
              else
                local processedText = applyEmojisWithCount(rawText, 1)
                service.insertText(edit, processTextEfficiency(processedText, _G.DictationLang))
              end
            end
          end
        end
        task(100, function() if _G.ContinuousDictationRunning then iniciarDictado() end end)
      end,
      onError = function() task(500, function() if _G.ContinuousDictationRunning then iniciarDictado() end end) end,
      onReadyForSpeech = function() 
        playSystemBeep()
      end
    }
    recognizer.setRecognitionListener(luajava.createProxy("android.speech.RecognitionListener", listener))
    recognizer.startListening(intent)
  end)
end

iniciarDictado()
return true
